<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>渲染器</title>
</head>
<body id="app">
  <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js">
  </script>
  <script>
    const { effect, ref} = VueReactivity

    function shouldSetAsProps(el, key, value) {
        if (key === 'form' && el.tagName === 'INPUT') return false
        return key in el
      }

    function createRenderer(options) {

      function patch(n1, n2, container) {
        if (n1 && n1.type !== n2.type) {
          unmount(n1)
          n1 = null
        }

        const { type } = n2
        // const Text = Symbol()
        // const newVnode = {
        //   type: Text,
        //   children: '我是文本内容'
        // }
        // const Comment = Symbol()
        // const newVnode = {
        //   type: Comment,
        //   children: '我是注释内容'
        // }
        // const Fragment = Symbol()
        // const vnode = {
        //   type: Fragment,
        //   children: [
        //     {type: li, children: 'text1'},
        //     {type: li, children: 'text2'},
        //     {type: li, children: 'text3'},
        //   ]
        // }

        if (typeof type === 'string') {
          if (!n1) {
            mountElement(n2, container)
          }else{
            patchElement(n1, n2)
          }
        } else if(typeof type === Text) {
          if (!n1) {
            const el = n2.el = createText(n2.children)
            insert(el, container)
          }else{
            const el = n2.el = n1.el
            if (n2.children !== n1.children) {
              setText(el, n2.children)
            }
          }
        } else if(type === Fragment) {
          if (!n1) {
            n2.children.forEach(c => patch(null, c, container))
          }else{
            patchChildren(n1, n2, container)
          }
        }
      }

      function patchElement(n1, n2) {
        const el = n2.el = n1.el
        const oldProps = n1.props
        const newProps = n2.props
        for (const key in newProps) {
          if (newProps[key] !== oldProps[key]) {
            patchProps(el, key, oldProps[key], newProps[key])
          }
        }
        for (const key in oldProps) {
          if (!(key in newProps)) {
            patchProps(el, key, oldProps[key], null)
          }
        }
        patchChildren(n1, n2, el)
      }

      function patchChildren(n1, n2, container) {
        if (typeof n2.children === 'string') {
          if (Array.isArray(n1.children)) {
            n1.children.forEach(c => unmount(c))
          }
          setElementText(container, n2.children)
        }else if(Array.isArray(n2.children)) {
          if (Array.isArray(n1.children)) {
            n1.children.forEach(c => unmount(c))
            n2.children.forEach(c => patch(null, c, container))
          }else{
            setElementText(container, '')
            n2.children.forEach(c => patch(null, c, container))
          }
        }else{
          if (Array.isArray(n1.children)) {
            n1.children.forEach(c => unmount(c))
          }else if(typeof n1.children === 'string') {
            setElementText(container, '')
          }
        }
      }

      const {
        createElement,
        setElementText,
        patchProps,
        createText,
        setText,
        insert
      } = options

      // 挂载
      function mountElement(vnode, container) {
        const el = vnode.el = createElement(vnode.type)
        if (typeof vnode.children === 'string') {
          setElementText(el, vnode.children)
        }else if(Array.isArray(vnode.children)) {
          vnode.children.forEach(child => {
            patch(null, child, el)
          });
        }
        if (vnode.props) {
          for (const key in vnode.props) {
            patchProps(el, key, null, vnode.props[key])
          }
        }
        insert(el, container)
      }

      // 卸载
      function unmount(vnode) {
        if (typeof vnode.type === Fragment) {
          vnode.children.forEach(c => unmount(c))
          return
        }
        const parent = vnode.el.parentNode
        if(parent) parent.removeChild(vnode.el)
      }

      function render(vnode, container) {
        if (vnode) {
          patch(container._vode, vnode, container)
        }else{
          if (container._vnode) {
            unmount(container._vnode)
          }
        }
        container._vnode = vnode
      }
      return {
        render
      }
    }

    
    const renderer = createRenderer({
      createElement(tag) {
        return document.createElement(tag)
      },
      setElementText(el, text) {
        el.textContent = text
      },
      insert(el, parent, anchor = null) {
        parent.appendChild(el)
        // parent.insertBefore(el, anchor)
      },
      createText(text) {
        return document.createTextNode(text)
      },
      setText(el, text) {
        el.nodeValue = text
      },
      patchProps(el, key, prevValue, nextValue) {
        if (/^on/.test(key)) {
          let invokers = el._vei || (el._vei = {})
          let invoker = invokers[key]
          const name = key.slice(2).toLowerCase()
          if (nextValue) {
            if (!invoker) {
              invoker = el._vei[key] = (e) => {
                // timeStamp事件发生的时间
                console.log(e.timeStamp < invoker.attached);
                if (e.timeStamp < invoker.attached) {
                  console.log(111);
                  return
                }
                if (Array.isArray(invoker.value)) {
                  invoker.value.forEach(fn => fn(e))
                }else{
                  invoker.value(e)
                }
              }
              invoker.value = nextValue
              // 存储时间处理函数被绑定的时间
              invoker.attached = performance.now()
              el.addEventListener(name, invoker)
            } else{
              invoker.value = nextValue
            }
          }else if (invoker) {
            el.removeEventListener(el, invoker)
          }
          
        }else if (key === 'class') {
          el.className = nextValue || ''
        }else if (shouldSetAsProps(el, key, nextValue)) {
          const type = typeof el[key]
          if (type === 'boolean' && nextValue === '') {
            el[key] = true
          }else{
            el[key] = nextValue
          }
        }else{
          el.setAttribute(key, nextValue)
        }
      }
    })

    // const vnode = {
    //   type: 'h1',
    //   props: {
    //     id: 'foo'
    //   },
    //   children: [
    //     {
    //       type: 'p',
    //       props: {
    //         onClick: [
    //           () => {
    //             alert('click 1')
    //           },
    //           () => {
    //             alert('click 2')
    //           }
    //         ],
    //       },
    //       children: 'hello'
    //     }
    //   ]
    // }
    const bol = ref(false)
    const el = document.getElementById('app')
    effect(() => {
      const vnode = {
        type: 'div',
        props: bol.value ? {
          onClick: () => {
            alert('父元素 click')
          }
        } : {},
        children: [
          {
            type: 'p',
            props: {
              onClick: () => {
                bol.value = true
              }
            },
            children: 'text'
          }
        ]
      }
      renderer.render(vnode, document.querySelector('#app'))
    })
    
    
  </script>
</body>
</html>